Tensorflow Computing Graph(v1 & v2)

정적 계산 그래프(Static Computing Graph) & 동적 계산 그래프(Dynamic Computing Graph)
정적 계산 그래프는 내부적으로 그래프를 잘 최적화할 수 있고, 다양한 하드웨어 장치를 지원하는 등의 장점이 있다.
하지만, 정적 그래프는 그래프 선언과 그래프 실행 단계로 나누어져 있어서 신경망 개발이 번거롭다.

텐서플로 2.0 부터 동적 계산 그래프를 기본 동작한다.
동적 계산 그래프는 그래프 선언과 실행을 번갈아 수행하기 때문에 파이썬과 넘파이 사용자들에게 훨씬 자연스럽게 느껴진다.

텐서플로 2.0에서도 tf.compat 모델에서 이전 v1.xAPI도 제공한다.
유향 비순환 그래프(Directed Acyclic Graph, DAG)
텐서플러는 유향 비순환 그래프를 기반으로 계산을 수행한다.
텐서플로 v1.x에서는 이러한 그래프는 저수준 API로 명시적으로 정의할 수 있다.
계산 그래프(Computing Graph)
계산 그래프는 노드들의 네트워크로 각 노드는 한 개 이상의 입력 텐서를 받고 0개 이상의 출력 텐서를 반환하는 연산을 수행한다.
텐서플로는 이런 계싼 그래프를 구성하고 이를 이용해서 그레디언트를 계산한다.
Deep Neural Networks As Computational Graphs | by Tyler Elliot Bettilyon |  Teb's Lab | Medium
텐서플로 v1.x 그래프
텐서플로 초기 버전(v1.x) 저수준 API에서는 그래프를 명시적으로 정의해야 한다.

1. 새로운 빈 계산 그래프 생성
2. 계산 그래프에 노드(텐서나 연산)을 추가
3. 그래프를 평가(실행)한다.
    a. 새로운 세션을 시작
    b. 그래프 내 변수를 초기화
    c. 이 세션에서 계산 그래프를 실행
z=2x(a-b)+c 식을 평가하기 위한 그래프(v1.x)_정적 그래프
import tensorflow as tf
g=tf.Graph()
with g.as_default(): #
a=tf.constant(1, name='a')
b=tf.constant(2, name='b')
c=tf.constant(3, name='c')
z=2*(a-b)+c
with g.as_default를 통해 그래프 g에 노드를 추가하였다.
만약 명시적으로 그래프를 만들지 않으면, 변수와 계산이 자동으로 기본 그래프에 추가된다.
(tf.compat.t1.get_default_graph()로 확인)

그래프의 연산과 텐서를 실행할 수 있는 환경을 세션이라고 한다.
Session 클래스는 v2에서 삭제되었기 때문에 사용하기 위해서 tf.compat.v1.Session()을 사용
Session(graph=g) 처럼 그래프 객체를 매개변수로 지정(매개변수 지정해주지 않으면, 기본 그래프를 사용)
with tf.compat.v1.Session(graph=g) as sess:
print(': z=', sess.run(z))

결과: z= 1

그래프에서 특정 노드에서 eval() 메서드를 호출할 수 있다.
평가할 때, 텐서플로는 해당 노드까지 그래프에 있는 모든 이전 노들르 시행한다.

한 개 이상의 플레이스홀더(placeholder) 변수가 있는 경우 세션의 run() 메서드를 통해 변수 값을 제공해야 한다.
z=2x(a-b)+c 식을 평가하기 위한 그래프(v2.x)_동적 그래프, 즉시 실행
a=tf.constant(1, name='a')
b=tf.constant(2, name='b')
c=tf.constant(3, name='c')
z=2*(a-b)+c
tf.print(': z= ', z)

결과: z=  1

텐서플로 v1에서는 텐서를 연산하기 위해서 직접 그래프를 만들고, 만들어진 그래프에 대한 세션에서 연산을 수행해야 했다.
텐서플로 v2에서는 텐서를 단순히 연산해주면, 즉시 실행이 가능하다.
입력 데이터를 모델에 주입(v1.x)
텐서플로 v.2에서는 파이썬 변수나 넘파이 배열로 데이터를 모델에 바로 주입할 수 있다.
텐서플로 v.1 저수준 API에서는 입력 데이터를 모델에 전달하기 위해 플레이스홀더 변수를 만들어야 한다.
g=tf.Graph()
with g.as_default():
a=tf.compat.v1.placeholder(shape=None, dtype=tf.int32, name='tf_a')
b=tf.compat.v1.placeholder(shape=None, dtype=tf.int32, name='tf_b')
c=tf.compat.v1.placeholder(shape=None, dtype=tf.int32, name='tf_c')
z=2*(a-b)+c
with tf.compat.v1.Session(graph=g) as sess:
feed_dict={a:1, b:2, c:3}
print(': z = ',sess.run(z, feed_dict=feed_dict))

결과: z =  1

플레이스홀더: scanf 처럼 입력을 받을 수 있는 변수
입력 데이터를 모델에 주입(v2.x)
텐서플로 v2에서는 a, b, c를 매개변수로 가지는 보통의 파이썬 함수를 정의하여 계산할 수 있다.
def compute_z(a, b, c):
r1=tf.subtract(a, b)
r2=tf.multiply(2, r1)
z=tf.add(r2, c)
return z
텐서플로의 subtract, multiply, add와 같은 함수는 높은 랭크를 가진 입력을 처리할 수 있다.
텐서플로의 Tensor객체, 넘파이 배열, 리스트, 튜플 등 모두 입력 가능
tf.print(' :', compute_z(1, 2, 3))
tf.print(' 1 :', compute_z([1], [2], [3]))
tf.print(' 2 :', compute_z([[1]], [[2]], [[3]]))

스칼라 입력: 1

랭크 1 입력: [1]

랭크 2 입력: [[1]]

텐서플로 v2는 텐서플로 v1에 비해 프로그래밍 스타일이 단순해지고, 명시적인 그래프와 세션 생성 단계를 생략할 수 있다.

하지만 텐서플로 v2의 동적 계산 그래프 모드로 계산하면, 정적 계산 그래프 만큼 효율적이지는 않다.
따라서 텐서플로 v2는 빠른 실행을 위해 파이썬 코드를 텐서플로 그래프 코드로 자동 변환해 주는 AutoGraph 도구를 제공한다.
또한 보통의 파이썬 함수를 텐서플로의 정적 그래프로 컴파일 하는 방법을 제공한다.